package com.sinohb.autonomousdriving_test.comutil;

import android.util.Log;

import java.io.DataInputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

public class ComUtil {
    /**
     * @brief => TestApp(Client)::SENT:[char]
     *        -> AndroidApp(Server)::RECV[char] -> AndroidApp(Client)::SENT[char]
     *        -> QtApp(Server)::RECV[char] -> QtApp(Server)::SENT[char]
     *        -> AndroidApp(Client)::RECV[char] -> AndroidApp(Server)::SENT[char]
     *        -> TestApp(Client)::RECV:[char]
     * @note assume communicate using just char-int */
    public static void openSocketChannel(int local_PORT, String destServer_IP, int destServer_PORT, OnSocketMsgRecvListener recvListener) {
        new Thread(() -> {
            try {
                ServerSocket server = new ServerSocket(local_PORT);
                Log.d("SocketServer", "Server started, listening on port: " + local_PORT);
                boolean doTransfer = destServer_IP != null && destServer_PORT > 0;
                Socket transferClient = null;
                DataInputStream transferClientReader = null;
                OutputStream transferClientWriter = null;
                if (doTransfer) {
                    transferClient = new Socket(destServer_IP, destServer_PORT);
                    Log.d("Transfer-SocketClient", "transfer-socket-client connected to server: " + transferClient);
                    transferClientReader = new DataInputStream(transferClient.getInputStream());
                    transferClientWriter = transferClient.getOutputStream();
                }
                while (true) {
                    Socket socket = server.accept();// if only one connection and always keep alive, can put it outside the while loop
                    DataInputStream reader = new DataInputStream(socket.getInputStream());
                    OutputStream writer = socket.getOutputStream();
                    try {
                        byte feature = reader.readByte();
                        byte type = reader.readByte();
                        int intValue = -1;
                        float floatValue = -1;
                        boolean isFloatType = type == 0x02;
                        if (isFloatType) {// float
                            floatValue = reader.readFloat();
                            Log.d("SocketServer", "Server-RECV:[" + floatValue + "]");
                        } else {// int
                            intValue = reader.readInt();
                            Log.d("SocketServer", "Server-RECV:[" + intValue + "]");
                        }
                        ByteBuffer buffer = ByteBuffer.allocate(6);
                        buffer.put(feature);
                        buffer.put(type);
                        if (isFloatType)
                            buffer.putFloat(floatValue);
                        else
                            buffer.putInt(intValue);
                        byte[] data = buffer.array();
                        if (!doTransfer) {
                            writer.write(data);
                            writer.flush();
                            if (isFloatType)
                                Log.d("SocketServer", "Server-RESP:[" + floatValue + "]");
                            else
                                Log.d("SocketServer", "Server-RESP:[" + intValue + "]");
                        } else {
                            transferClientWriter.write(data);
                            transferClientWriter.flush();
                            if (isFloatType)
                                Log.d("Transfer-SocketClient", "Transfer-SENT:[" + floatValue + "]");
                            else
                                Log.d("Transfer-SocketClient", "Transfer-SENT:[" + intValue + "]");
                            feature = transferClientReader.readByte();
                            type = transferClientReader.readByte();
                            isFloatType = type == 0x02;
                            if (isFloatType) {// float
                                floatValue = reader.readFloat();
                                Log.d("Transfer-SocketClient", "Transfer-RECV:[" + floatValue + "]");
                            } else {// int
                                intValue = reader.readInt();
                                Log.d("Transfer-SocketClient", "Transfer-RECV:[" + intValue + "]");
                            }
                            writer.write(data);
                            writer.flush();
                            if (isFloatType)
                                Log.d("SocketServer", "RESP:[" + floatValue + "]");
                            else
                                Log.d("SocketServer", "RESP:[" + intValue + "]");
                        }
                        recvListener.onRecv(feature, type, intValue, floatValue);
                    } catch (Exception e) {
                        if (e.getMessage() == null || e.getMessage().contains("Broken pipe")) {
                            Log.w("SocketServer", "Dest Server is broken(reconnecting...): " + e.getMessage(), e);
                            transferClient = new Socket(destServer_IP, destServer_PORT);
                            transferClientReader = new DataInputStream(transferClient.getInputStream());
                            transferClientWriter = transferClient.getOutputStream();
                        }
                        Log.w("SocketServer", "Error Occur", e);
                    }

                    writer.close();
                    reader.close();
                    socket.close();

//                    if (originalMsg == some-self-defined-code) {
//                        Log.i("SocketServer", "server received 'END', shutting down");
//                        server.close();
//                        if (doTransfer) {
//                            transferClientWriter.close();
//                            transferClientReader.close();
//                            transferClient.close();
//                        }
//                        break;
//                    }
                }
            } catch (Exception e) {
                Log.e("SocketServer", "Error: " + e.getMessage(), e);
            }
        }).start();
    }

    public static Map<String, String> getLocalIP() {
        Map<String, String> map = new HashMap<>();
        try {
            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
            while (interfaces.hasMoreElements()) {
                NetworkInterface networkInterface = interfaces.nextElement();
                String interfaceName = networkInterface.getName();
                // only check wlan0 and eth0
                if (interfaceName.equals("wlan0") || interfaceName.equals("eth0")) {
                    Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
                    while (addresses.hasMoreElements()) {
                        InetAddress addr = addresses.nextElement();
                        // filter IPv4 addresses(not looped)
                        if (!addr.isLoopbackAddress() && addr.getAddress().length == 4) {
                            if (interfaceName.equals("eth0"))
                                map.put("eth0", addr.getHostAddress());
                            else if (interfaceName.equals("wlan0"))
                                map.put("wlan0", addr.getHostAddress());
                        }
                    }
                }
            }
        } catch (SocketException e) {
            e.printStackTrace();
        }
        return map;
    }
}
